import { PortalTarget } from 'portal-vue';
import { GlCollapsibleListbox } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import VulnerabilityReport from 'ee/security_dashboard/components/shared/vulnerability_report/vulnerability_report.vue';
import projectVulnerabilitiesQuery from 'ee/security_dashboard/graphql/queries/project_vulnerabilities.query.graphql';
import VulnerabilityCounts from 'ee/security_dashboard/components/shared/vulnerability_report/vulnerability_counts.vue';
import VulnerabilityFilters from 'ee/security_dashboard/components/shared/vulnerability_report/vulnerability_filters.vue';
import VulnerabilityListGraphql from 'ee/security_dashboard/components/shared/vulnerability_report/vulnerability_list_graphql.vue';
import { FILTER_PRESETS } from 'ee/security_dashboard/components/shared/vulnerability_report/constants';
import * as CommonUtils from '~/lib/utils/common_utils';

describe('Vulnerability report component', () => {
  let wrapper;
  const createWrapper = ({
    query = projectVulnerabilitiesQuery,
    fields = [],
    filterDropdowns = [],
    isVisible = true,
    showCounts = true,
    filterFn,
    isProjectVulnerabilityReport = true,
    glFeatures = {},
  } = {}) => {
    wrapper = shallowMountExtended(VulnerabilityReport, {
      provide: {
        isProjectVulnerabilityReport,
        glFeatures,
      },
      propsData: {
        query,
        fields,
        filterDropdowns,
        isVisible,
        showCounts,
        filterFn,
      },
    });
  };

  const findCounts = () => wrapper.findComponent(VulnerabilityCounts);
  const findPortalTarget = () => wrapper.findComponent(PortalTarget);
  const findFilters = () => wrapper.findComponent(VulnerabilityFilters);
  const findList = () => wrapper.findComponent(VulnerabilityListGraphql);
  const findListTopDiv = () => wrapper.findByTestId('vulnerability-list-top');

  describe('counts', () => {
    it.each([true, false])('shows/hides the counts', (showCounts) => {
      createWrapper({ showCounts });

      expect(findCounts().exists()).toBe(showCounts);
    });

    it('bubbles up the counts-changed event', () => {
      createWrapper();
      const data = {};
      findCounts().vm.$emit('counts-changed', data);

      expect(wrapper.emitted('counts-changed')[0][0]).toBe(data);
    });
  });

  describe('portal target', () => {
    it.each([true, false])('shows/hides the portal', (isVisible) => {
      createWrapper({ isVisible });

      expect(findPortalTarget().exists()).toBe(isVisible);
    });

    it('uses the same portal name as the vulnerability list', () => {
      createWrapper();

      expect(findPortalTarget().props('name')).toBe(findList().props('portalName'));
    });
  });

  describe('filters', () => {
    it('gets the expected filters prop', () => {
      const filterDropdowns = [];
      createWrapper({ filterDropdowns });

      expect(findFilters().props('filters')).toBe(filterDropdowns);
    });

    it('calls the filter transform function when the filters are changed', async () => {
      const fnData = { b: 3, d: 4 };
      const filterFn = jest.fn().mockImplementation(() => fnData);
      createWrapper({ filterFn });
      const data = { a: 1, b: 2 };
      findFilters().vm.$emit('filters-changed', data);
      await nextTick();

      // We test that the filter function is called with the data emitted by VulnerabilityFilters.
      // Then we test that the list gets the value returned by filterFn (the transformed value).
      expect(filterFn).toHaveBeenCalledTimes(1);
      expect(filterFn).toHaveBeenCalledWith(data);
      expect(findList().props('filters')).toBe(fnData);
    });

    it('passes the filter data to the counts and list when the filters are changed', async () => {
      createWrapper();
      const data = {};
      findFilters().vm.$emit('filters-changed', data);
      await nextTick();

      expect(findCounts().props('filters')).toBe(data);
      expect(findList().props('filters')).toBe(data);
    });
  });

  describe('vulnerability list', () => {
    it('gets the expected props', () => {
      const fields = [];
      createWrapper({ fields });

      expect(findList().props('fields')).toBe(fields);
    });

    it.each`
      filterDropdowns                       | expected
      ${FILTER_PRESETS.DEVELOPMENT}         | ${true}
      ${FILTER_PRESETS.DEVELOPMENT_PROJECT} | ${false}
      ${FILTER_PRESETS.OPERATIONAL}         | ${true}
      ${FILTER_PRESETS.OPERATIONAL_PROJECT} | ${false}
    `('gets the expected showProjectNamespace prop', ({ filterDropdowns, expected }) => {
      createWrapper({ filterDropdowns });

      expect(findList().props('showProjectNamespace')).toBe(expected);
    });

    it.each([true, false])('is shown/hidden when the isVisible prop is %s', (isVisible) => {
      createWrapper({ isVisible });

      expect(findList().exists()).toBe(isVisible);
    });

    it('bubbles up the vulnerability-clicked event', () => {
      createWrapper();
      const data = {};
      findList().vm.$emit('vulnerability-clicked', data);

      expect(wrapper.emitted('vulnerability-clicked')[0][0]).toBe(data);
    });
  });

  describe('scroll to top behavior', () => {
    beforeEach(() => {
      createWrapper();
    });

    it('has the target div to scroll to', () => {
      expect(findListTopDiv().exists()).toBe(true);
    });

    it('scrolls to the top of the list if the top of the list is below the viewport', () => {
      const div = findListTopDiv().element;
      jest.spyOn(div, 'getBoundingClientRect').mockReturnValue({ top: -51 });
      jest.spyOn(CommonUtils, 'contentTop').mockReturnValue(50);
      const spy = jest.spyOn(CommonUtils, 'scrollToElement');
      findList().vm.$emit('query-variables-changed');

      expect(spy).toHaveBeenCalledTimes(1);
      expect(spy).toHaveBeenCalledWith(div);
    });

    it('does not scroll to the top of the list if the top of the list is above the viewport', () => {
      const div = findListTopDiv().element;
      jest.spyOn(div, 'getBoundingClientRect').mockReturnValue({ top: 46 });
      jest.spyOn(CommonUtils, 'contentTop').mockReturnValue(45);
      const spy = jest.spyOn(CommonUtils, 'scrollToElement');
      findList().vm.$emit('query-variables-changed');

      expect(spy).not.toHaveBeenCalled();
    });
  });

  describe('group by', () => {
    const findGroupByButton = () => wrapper.findComponent(GlCollapsibleListbox);

    const createWrapperWithButtonEnabled = () => {
      createWrapper({
        isProjectVulnerabilityReport: true,
        glFeatures: { vulnerabilityReportGrouping: true },
      });
    };

    it.each`
      vulnerabilityReportGrouping | isProjectVulnerabilityReport | shouldDisplay
      ${false}                    | ${false}                     | ${false}
      ${true}                     | ${false}                     | ${false}
      ${false}                    | ${true}                      | ${false}
      ${true}                     | ${true}                      | ${true}
    `(
      'displays the group by button correctly ' +
        'ff: $vulnerabilityReportGrouping, ' +
        'project: $isProjectVulnerabilityReport, ' +
        'should display: $shouldDisplay',
      ({ vulnerabilityReportGrouping, isProjectVulnerabilityReport, shouldDisplay }) => {
        createWrapper({
          isProjectVulnerabilityReport,
          glFeatures: { vulnerabilityReportGrouping },
        });

        expect(findGroupByButton().exists()).toBe(shouldDisplay);
      },
    );

    it('displays the group by label', () => {
      createWrapperWithButtonEnabled();
      expect(wrapper.findByText('Group by:').exists()).toBe(true);
    });

    it('passes the correct items', () => {
      createWrapperWithButtonEnabled();
      expect(findGroupByButton().props('items')).toEqual([
        { value: '', text: 'None' },
        { value: 'status', text: 'Status' },
        { value: 'severity', text: 'Severity' },
      ]);
    });
  });
});
